package com.boarsoft.boar.agent.apollo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.boarsoft.common.util.JsonUtil;
import com.boarsoft.common.util.StreamUtil;

/**
 * 通过长轮询监听服务端配置的变化，并在配置变化时更新本地文件
 * 
 * @author Mac_J
 *
 */
public class ApolloClient implements Runnable {
	private static final Logger log = LoggerFactory.getLogger(ApolloClient.class);

	protected ScheduledExecutorService scheduler;
	/**
	 * Apollo config server notification api url<br>
	 * 此的配置格式如下：<br>
	 * http://{config_server_url}/notifications/v2?appId={appId}&cluster={clusterName}&notifications=<br>
	 * config_server_url：Apollo配置服务的地址<br>
	 * appId：应用的appId<br>
	 * clusterName：集群名<br>
	 * notifications：需要监听的namespace列表，格式如下：<br>
	 * [{"namespaceName": "application", "notificationId": 100}]<br>
	 * 对于properties类型的namespace，只需要传入namespace的名字<br>
	 * 对于其它类型的namespace，需要传入namespace的名字加上后缀名，如datasources.json
	 */
	protected String url;
	protected String secret;
	protected String encIn = "UTF-8";
	protected String appId;
	protected ApolloPuller puller;
	protected List<Notification> notifications;

	protected Future<?> future;

	@PostConstruct
	public void init() {
		log.info("Schedule pulling config from apollo server with {}", url);
		// 一分钟后开始循环拉取
		future = scheduler.scheduleWithFixedDelay(this, 60L, 60L, TimeUnit.SECONDS);
	}

	@PreDestroy
	public void destroy() {
		future.cancel(true);
	}

	@Override
	public void run() {
		PrintWriter out = null;
		BufferedReader br = null;
		HttpURLConnection conn = null;
		String s = url;
		try {
			s.concat(URLEncoder.encode(JsonUtil.from(notifications), encIn));
			log.debug("Pulling config file from apollo server with {}", s);
			conn = (HttpURLConnection) new URL(s).openConnection();
			conn.setConnectTimeout(3000);// 连接3秒超时
			conn.setReadTimeout(63000);// 读为63秒超时，必须大于60秒
			conn.setRequestProperty("User-Agent", "Mozilla/5.0");
			conn.setRequestProperty("Content-Type", encIn);
			conn.setRequestMethod("GET");
			// HTTP请求头
			Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
			if (headers != null && headers.size() > 0) {
				for (String h : headers.keySet()) {
					conn.setRequestProperty(h, headers.get(h));
				}
			}
			// 不需要报文体
			// out = new PrintWriter(conn.getOutputStream());
			// out.print(body);
			// out.flush();
			StringBuilder sb = new StringBuilder();
			br = new BufferedReader(new InputStreamReader(conn.getInputStream(), encIn));
			String line = null;
			while ((line = br.readLine()) != null) {
				// sb.append(line).append(FileUtil.LINE_SEPARATOR);
				sb.append(line).append("\n");
			}
			switch (conn.getResponseCode()) {
			case 200: // 配置有变化，更新配置
				
				break;
			case 304: // 配置无变化
				break;
			}
		} catch (UnsupportedEncodingException e) {
			log.error("Unsupport encoding {}", encIn);
		} catch (IOException e) {
			log.error("Error on watch apollo with {}", url, e);
		} finally {
			StreamUtil.close(br);
			StreamUtil.close(out);
			if (conn != null) {
				conn.disconnect();
			}
		}
	}

	public void setScheduler(ScheduledExecutorService scheduler) {
		this.scheduler = scheduler;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	public String getEncIn() {
		return encIn;
	}

	public void setEncIn(String encIn) {
		this.encIn = encIn;
	}

	public String getAppId() {
		return appId;
	}

	public void setAppId(String appId) {
		this.appId = appId;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public ApolloPuller getPuller() {
		return puller;
	}

	public void setPuller(ApolloPuller puller) {
		this.puller = puller;
	}

	public ScheduledExecutorService getScheduler() {
		return scheduler;
	}

	public List<Notification> getNotifications() {
		return notifications;
	}

	public void setNotifications(List<Notification> notifications) {
		this.notifications = notifications;
	}
}